Skip to content

Place GOPACS redispatch orders through Distro Energy - #135

Open
Miggets7 wants to merge 26 commits into
mainfrom
feature/distro-gopacs-orders
Open

Miggets7 wants to merge 26 commits into
mainfrom
feature/distro-gopacs-orders

Conversation

@Miggets7

Copy link
Copy Markdown
Contributor

Description

Closes #133. Part of openremote/denhaag#209, which stays open until the orders are tested on production.

Adds DistroGopacsHandler, which places GOPACS redispatch orders through Distro Energy's GOPACS Connector API. It is switched on per EmsDistroEnergyAsset with gopacsEnabled. EmsOptimisationService copies the redispatch announcement from the EmsGOPACSAsset to every EmsDistroEnergyAsset under the same parent, so a deployment needs no attribute links and the GOPACS and Distro handlers stay independent.

The handler pre-fills order type and quantity from the announcement and the batteries. The operator sets a price and confirms, and nothing is ordered automatically. After that it polls the order status, replaces open orders when the price changes, and cancels them when the announcement closes, on a manual cancel, or when gopacsEnabled is turned off or the asset is deleted. Setup is described in ems/docs/DistroEnergyIntegration.md.

JAX-RS proxy and wire records for creating, reading, replacing and
cancelling GOPACS orders and listing their settlements. Status and
order type stay strings so new values in the draft API do not break
deserialisation.
Announcement inputs are read-only and meant to be filled through
attribute links from an EmsGOPACSAsset, so the Distro integration does
not depend on the GOPACS handlers. Order type and quantity are
pre-filled by the handler and can be overridden; price, confirm and
cancel are set by the operator. The existing day-ahead panels are
renamed so both groups read clearly in the asset view.
Pre-fills order type and quantity from the linked announcement: the
EAN effectivity category picks the side, and the batteries under the
parent optimisation asset size a constant power that their state of
charge can hold for the whole window. On operator confirmation the
order is posted to Distro; a price change replaces open orders, a
closed announcement or the cancel flag cancels them, and a poll tracks
status until a final one, recording the trades of settled orders.

Tokens come from OpenRemote's OAuth filter, which caches and refreshes
the client-credentials token.
One handler per EmsDistroEnergyAsset while gopacsEnabled is true,
reconciled from both the persistence route and attribute events under
a lock, independent of the day-ahead handler. Other gopacs* attribute
events are forwarded to the running handler.
Unit spec for the effectivity-to-side mapping, battery sizing,
expiration and the attribute-driven create/replace/cancel/poll flows;
WireMock spec for the OAuth token call, bearer reuse and wire format;
service spec for the gopacsEnabled reconcile.
Configuration, the attribute links from EmsGOPACSAsset, order sizing,
the order lifecycle and the new tests. Also replace the made-up
effectivity example in the GOPACS docs with a real category.
Track an order as PENDING before posting it, so a create that times
out after Distro accepted it is still polled, cancelled and settled.
Only a 4xx untracks it again.

Store a polled order before fetching its settlements, so a failed fetch
is retried next poll without repeating the STATUS entry. Read every
settlements page. Skip the suggestion when the batteries cannot hold
the 100 kW minimum. Drop queued work once the handler is undeployed and
close the client only after a running order action has finished.
Day-ahead stays on the left, so the two Distro features no longer
interleave on the asset page.
Operators work in these two panels, so they sit next to the day-ahead
panels while configuration, status and history stay on the right.
Turning gopacsEnabled off or deleting the asset stopped polling but left
PENDING and SUBMITTED orders live at Distro, where they could still be
matched with nothing tracking them. Both now cancel the open orders
before closing the client. A manager stop still leaves them in place,
since the next start restores and keeps tracking them.
The updated Distro spec takes at most one decimal for the quantity in MW
and two for the price, requires a price of 0 or more, needs the order
to expire at least 15 minutes before start, and only lets SUBMITTED
orders be replaced.

Round an operator-entered quantity down to 100 kW steps and the price
to cents. Refuse a negative price, on confirm and on a price change,
and refuse a window that starts within 15 minutes. Replace only the
SUBMITTED orders of the announcement.
GOPACS no longer takes bids for an announcement once its bid validity
end has passed. Such an order went out with Distro's default expiry and
stayed live without any chance to help. Refuse it when the validity end
is known.
The updated Distro spec says the settlements endpoint is not implemented
yet. A settled order whose trade fetch failed stayed tracked for a
retry, so it would have been polled every five minutes forever. Record
the failure as SETTLEMENTS_FAILED and drop the order instead.
The handler built a pending GopacsOrder from a request and a replace
request from an order by hand. GopacsOrder.pending(request) and
toRequest(price) now own those copies.
Routing every gopacs* attribute also sent the handler's own writes
(orders, history, last sync, the pre-filled type and quantity) back to
it, and each queued a task that did nothing. Route on the explicit
DistroGopacsHandler.INPUTS set instead.
Build the three updateSuggestion log messages with String.format
instead of long string concatenations.
An undeploy less than a second after a confirm or cancel skipped the
delayed reset, leaving the flag true. Setting it to true again after
re-enabling was then an unchanged value and was ignored. Deploy now
resets either flag that is still true.
Log the handler config at deploy (without the secret), every changed
input at FINE, and confirms, cancels, placed and replaced orders and
status changes at INFO. Warn when a flag is ignored because it is
already true or because no handler is running for the asset.
A bare HTTP 403 does not say whether the tenant, the IP whitelist or the
URL is wrong. Log Distro's error body, cut to 500 characters, and record
it in the order history. HTTP errors are logged without the stack trace,
which adds nothing for them.
gopacsOrderType was free text, so an operator could type anything and
only learn at confirm time that it was refused. An enum value type
renders as a dropdown in the manager UI. The handler now works with the
enum and only turns it into a string for the Distro request.
placeOrder built the refusal reason through a long else-if chain. The
checks now live in a static validateInput that returns early, so
placeOrder only reads the attributes, refuses or sends.

The quarter-hour alignment check is gone: the window is linked from the
GOPACS announcement and is read-only, so it is always aligned. The
separate rounding log is folded into the Placing log, which now also
shows the quantity the operator entered.
gopacsOrderPrice is now a POSITIVE_NUMBER (min 0). OpenRemote validates
every attribute event against the value constraints, from the UI as
well as from rules and links, so a negative price never reaches the
handler. The handler's own negative-price checks on create and replace
are removed.
CI runs spotlessCheck, which failed on both test files. Formatting only.
Distro confirmed the settlement endpoints go live shortly and that the
trades are available as soon as an order is SETTLED. The Javadoc and
docs no longer say the endpoints are missing. Behaviour is unchanged:
one fetch per settled order, no retry.
The Distro GOPACS order handler reads the announcement from six
read-only gopacs* inputs, but nothing in the extension filled them:
each deployment had to add attribute links from the EmsGOPACSAsset by
hand and copy the current values once.

EmsOptimisationService now copies contractedEAN and the redispatch
announcement attributes to every EmsDistroEnergyAsset that shares the
GOPACS asset's parent. It does so on a change of one of those GOPACS
attributes, on a save of either asset, and at start. Only inputs that
differ are written, so the redispatch poller rewriting the same
announcement does not rewrite the Distro asset on every poll. Nothing
is copied while a parent has more than one GOPACS asset.
The Distro asset no longer needs attribute links from the GOPACS asset:
EmsOptimisationService copies the announcement to the Distro asset under
the same parent. The setup steps, attribute table and workflow diagram
now describe the copy.
@Miggets7 Miggets7 added the Feature Development of new feature label Oct 28, 2026
@Miggets7
Miggets7 requested review from igorrutka and wborn October 28, 2026 15:59
@Miggets7 Miggets7 self-assigned this Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Development of new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EMS: place GOPACS redispatch orders through Distro Energy

1 participant